Skip to content

Make the boilerplate resolve the packages it claims, and rename in one command - #13

Merged
anilcancakir merged 10 commits into
mainfrom
feature/fork-ready-boilerplate
Sep 13, 2026
Merged

anilcancakir merged 10 commits into
mainfrom
feature/fork-ready-boilerplate

Conversation

@anilcancakir

Copy link
Copy Markdown
Member

What was wrong

A fork of this repository was already resolving the wrong packages, silently.

pubspec.yaml pinned magic_notifications: ^0.0.2, which in Dart's caret semantics means
>=0.0.2 <0.1.0. magic_starter alpha.26 requires ^0.2.0. The two ranges do not intersect,
so pub did not fail: it walked the starter back to alpha.24 and exited 0. Every fork got a
build that resolved, ran, and was two releases stale, and nothing in CI could see it.

Most of the rest follows from looking closely at that:

  • alpha.24 introduced 20 magic_starter.titles.* translation keys this app never shipped,
    while route_service_provider.dart:29 registers the routes that need them unconditionally.
    Those titles have been rendering as raw key paths.
  • LocalizationServiceProvider and NotificationServiceProvider were both registered with no
    config file and no factory in main.dart.
  • supported_locales claimed ['en', 'tr'] while assets/lang/ held only en.json.
  • .claude/agents/component-visual-reviewer.md pointed three commands at
    <workspace>/lib/ui/components, missing this repo's own directory segment. That path does
    not exist, so every find and grep in its procedure returned nothing and reported a clean
    review.

What this changes

The graph a fork gets, and proof of it. Nine hosted constraints bumped, with
magic_starter and magic_notifications moved as one edit because a partial bump does not
solve. A new published CI job resolves hosted-only and asserts the resolved version out of
the lock, because pub get exits 0 on a downgrade and only the lock knows which version won.
pubspec.lock is now tracked as the hosted resolution, gated by run_lockfile in bin/check
plus a CI check that runs before pub get can rewrite the evidence.

app:rename. The fork checklist was nine manual steps that never mentioned macOS, Windows,
Linux, web, Info.plist, main.dart, dependabot.yml, or the Kotlin package directory move.
One command now rewrites 24 sites plus that directory move, derives the current identity from
the tree so a second run is a no-op, and refuses on a dirty worktree only when it is actually
writing. --dry-run is proven inert by hashing all 158 files in scope before and after.

Two documented patterns that had no instance. A controller implementing
SessionScopedController, so the SessionScopeSync.attach() call that has always run finally
has something to reset, and a status token family in the supplement shape DESIGN.md
describes.

file_picker is no longer pinned here. Nothing in this app imports it; it arrives through
magic's Pick facade. The pin was a second opinion on a transitive dependency, and when
magic's master moved to ^12.2.0 ahead of a release the two ranges stopped intersecting, so
local resolution failed outright and bin/check could not run at all. It now resolves from
whichever magic is in play: 12.2.0 locally, 11.0.3 hosted, which is what a fork was already
getting.

Verification

bin/check green, eight jobs. CHECK_ALLOW_HOSTED=1 bin/check green too, so the suite passes
against the packages a fork actually downloads and not just the local checkouts. app:rename
was run for real in a throwaway worktree: pub get left it clean, the rename rewrote 24 files
and moved the Kotlin package, flutter analyze came back clean against the hosted graph, and a
second identical run reported unchanged (24) with an empty diff. Tests went from 10 to 26.

Two gates were negative-controlled rather than assumed: the lockfile gate fires on a genuinely
path-poisoned lock with every other job green, and the hardened design-tokens scanner catches
a raw colour literal that the previous version passed clean.

Not verified

The dusk-driven pass at 1440px and 390px did not run. bin/fsa deadlocks on its own lock: it
acquires it, installs a trap ... EXIT to release it, then execs into the compiled binary at
bin/fsa:110, and exec replaces the shell so the trap never fires. Short commands self-heal
when the process exits; the long-running mcp:serve this project spawns never releases. The
dispatcher route works but repoints a shared ~/.artisan/state.json that other live sessions
were holding.

Static cover was built instead: every magic_starter.titles.* key the starter's source
references was compared against what this app ships, an exact 20-to-20 match, and a new
locale_parity_test.dart holds en and tr to identical key sets. That covers the cause of a
raw route title, not a widget-level key or a layout break at 390px.

The bin/fsa lock bug is not fixed here, since no part of this change owns that script, but
every fork inherits it.

magic_starter alpha.26 needs magic_notifications ^0.2.0, and this app pinned
^0.0.2, which means >=0.0.2 <0.1.0. The two do not intersect, so pub quietly
walked the starter back to alpha.24 instead of failing: a fork got a build that
resolved, ran, and was two releases stale. The two constraints move together or
not at all.

alpha.24 also introduced 20 magic_starter.titles.* keys that en.json never
gained, and route_service_provider.dart:29 registers the notification routes
ungated, so those titles have been rendering as raw keys.

The two generated files come along because they are functions of the resolved
graph rather than edits: flutter pub get performs a one-time SDK migration on
analysis_options.yaml, and the macOS registrant gains purchases_flutter now that
magic_payments arrives through the starter. The registrant records the LOCAL
graph, which is what keeps the working tree clean. Unlike pubspec.lock, no fork
ever consumes this file: any build regenerates it from whatever that fork
resolves, so the committed value is transient for them and permanent dirt here
would be the only lasting cost.
config/app.dart registers LocalizationServiceProvider and
NotificationServiceProvider, but neither had a config file and neither appeared
in main.dart's factory list, so both fell through to package defaults. The
notifications feature flag was off besides, which meant a dependency this app
pays for shipped undemonstrated.

supported_locales already claimed en and tr while assets/lang held only en.json.
That claim is now true. The parity between the two files is exact rather than
best effort because magic's Translator replaces its sentence map on load instead
of merging it with the fallback: a key missing from tr.json renders the raw key
path on screen, not the English string. locale_parity_test.dart is what keeps
that from drifting back.

auto_detect_locale stays off. A device set to a locale this boilerplate does not
ship has no tested path here, and the replace-not-merge behaviour above is
exactly what turns that into raw keys on every screen. A fork flips it once its
supported_locales covers the locales its users actually have.
Four independent things, all of which were wrong or missing today.

CI gains a published job. Resolving is not the check: pub exits 0 on a
downgrade as long as SOME version in range resolves, which is exactly how the
alpha.24 walk-back stayed green. The job reads the resolved version out of the
lock and asserts it, verified against a synthetic alpha.24 lock to confirm it
fails on the input it exists to catch.

The component-visual-reviewer agent pointed at /Users/anilcan/Code/fluttersdk/
lib/ui/components, missing this repo's own directory segment. That path does not
exist, so every find and grep in its procedure returned nothing and reported a
clean review. It was also the only absolute path in a repository whose whole
purpose is to be cloned somewhere else.

design.md described components as living in the magic_starter generic library on
one line and scoped the WindRecipe import rule to magic_starter files on another.
Both meant this app's own lib/ui/components/. The import rule itself was right
and is unchanged.

design-tokens rescanned raw on one trigger, an unterminated block comment at end
of file. That catches the OPEN case, which flutter analyze already makes
impossible in real Dart. The CLOSED case is the live one: a /* inside a
triple-quoted string that some later ordinary */ closes leaves the scanner
balanced at EOF, so nothing fires and the region between them is dropped in
silence. Measured against the pre-edit script, a raw literal placed that way
passed clean.
The lock was gitignored because one generated with pubspec_overrides.yaml
active encodes sibling paths that exist on one machine. That diagnosis was
right and the conclusion was not: the fix for a lock generated the wrong way is
to generate it the right way. Untracked, a fork's first build resolves whatever
the caret ranges happen to solve to on the day it clones, which is not what CI
proved green.

run_lockfile is what makes tracking safe. It reads the STAGED lock, not the
working tree, and fails on a source: path entry. That split is deliberate and
permanent: a local pub get puts sibling paths back in the lock every time, so
the working copy stays dirty and unstaged while the index holds the hosted
resolution. require_local_siblings reads the opposite state (the overrides file
must be PRESENT), and the two cannot wedge because regenerating hosted-only
moves that file aside and back rather than removing it.

Verified by staging the real local-path lock, 11 path entries, and confirming
the job fails with every other job green. An earlier probe that appended a
malformed line proved nothing: pub refused to parse it and the run aborted
before the gate executed.
app:rename rewrites 24 sites plus the Kotlin package directory: the Dart
package name and its import sites, the Android namespace and applicationId, the
iOS and macOS bundle identifiers, the three desktop build systems, the web
manifest and title, .env, lib/main.dart, DESIGN.md and dependabot.yml. It
derives the CURRENT identity from the tree rather than hardcoding it, which is
what makes a second run a no-op. --dry-run is proven inert by hashing every file
in scope before and after, not by reading git status, which cannot tell a
sibling's staged file from a write.

Rewrite rules scan for their anchor instead of visiting recorded line numbers.
That is why the command handles the sixth PRODUCT_BUNDLE_IDENTIFIER line in the
iOS project file, which the research inventory had missed.

Two things the repo documented and never demonstrated now have exactly one
instance each: a controller implementing SessionScopedController, so the
SessionScopeSync.attach() call that has always run finally has something to
reset, and a status token family in the supplement shape DESIGN.md describes.

The tracked macOS plugin registrant switches to the hosted variant. A fork
clones it, runs pub get against the published graph, and gets no diff, so
app:rename does not refuse on its first run. The local variant is what made it
refuse. The tracked lock already follows this rule for the same reason: what is
committed is the graph a fork resolves, not the one this machine does.
…aths

The status tokens asserted WCAG AA in their comments and missed it on four
of six pairs, measured: white on #16A34A is 3.30, near-black on #B45309 is
3.53, white on #0EA5E9 is 2.77, white on #0284C7 is 4.10. The cause was
assuming one foreground per role works in both modes, but the generated fills
do not hold lightness across modes and they move in opposite directions:
bg-success goes darker to lighter, bg-warning lighter to darker. So each
foreground flips by mode now, no generated fill changed, and every pair is
recomputed in the docblock: 5.93, 6.40, 5.02, 5.38, 5.57, 5.02.

Two paths could act without the operator seeing it. The identity read back out
of the tree was never validated, only the flags were, so a pubspec.yaml
carrying a traversal in its name reached a directory move; it is now held to
the same patterns. And the Kotlin package move skipped every non-File entity
and then deleted the source recursively, which would take a fork's subpackages
with it, silently, in a case this repo has no fixture for.

--display now rejects # and tab. They break nothing syntactically, they get
eaten: flutter_dotenv strips a trailing #comment, so --display='Acme #1' left
the app reading 'Acme' while the command's own re-read saw the whole string,
which quietly breaks idempotency.

TEST_HOST on macOS is rewritten after all. The constraint was to touch the
Xcode project only through anchored matches, and this rule anchors on the
setting name and the current value exactly like the bundle-id rule beside it,
so it is an instance of that constraint rather than an exception to it. Left
alone it pointed a renamed fork's RunnerTests target at a bundle that is no
longer produced.

The lockfile gate did not run in CI at all, because CI never invokes bin/check;
it now checks the committed lock directly, before pub get rewrites the
evidence. The dashboard card was duplicated between its loading and loaded
branches, differing only in indentation, and its two strings were hardcoded
English on the one screen the rules name as the pattern to copy.
Nothing in this app imports file_picker. It arrives through magic's Pick
facade, which makes the pin here a second opinion on a version this app has no
stake in, and second opinions on a transitive dependency only matter when they
disagree with the first one.

They did. magic's master moved to ^12.2.0 ahead of a release while this app
pinned ^11.0.2 to match published magic 0.0.9. Those ranges do not intersect,
so version solving failed outright against local sibling checkouts and
bin/check could not run at all, which is the state this repository was already
in before any of this work started.

Removing the pin resolves file_picker from whichever magic is in play:
transitive 12.2.0 against the path-bound local checkout, transitive 11.0.3
against published magic 0.0.9, which is the same version a fork was getting
before. Both graphs verified green with the full suite, and the day magic
publishes its file_picker 12 port nothing here needs editing.

The comment the pin carried argued a fork has no dependency_overrides safety
net so the constraint should be repeated here. That was true and still
insufficient: magic 0.0.9 already caps file_picker below 12.0.0-0, so the pin
never protected anything the framework was not already protecting.
@kodizm

kodizm Bot commented Sep 6, 2026

Copy link
Copy Markdown

Note

Kodizm (AI-generated). May contain mistakes; verify before acting.

The dependency fix and its CI proof hold up under hosted resolution, and the localisation work checks out exactly as claimed; one latent correctness bug in app:rename that this repo's own tree cannot trigger but a fork can.

Major

lib/app/commands/app_rename_command.dart:306 — correctness. _buildPlan appends _identityRewrites and _dartImportRewrites into the same changed list without merging by path, and both compute their content from the same on-disk source. _apply (line 668) then writes them in order, so when one path appears twice the second write discards the first. lib/main.dart is the only file in both generators: this repo's copy uses relative imports so the collision never fires, but a fork whose lib/main.dart self-imports package:<name>/... (exactly the case _dartImportRewrites exists to scan for, per its docblock at line 581) gets the import rewrite last and silently loses the MagicApplication(title: '...') display-name rewrite. The report prints lib/main.dart twice with plausible line counts, so it reads as done. Not covered: the fixture at test/app/commands/app_rename_command_test.dart:15 copies this repo's main.dart, which has no package: self-import.

Minor

.github/workflows/ci.yml:171 — maintainability. The published job hard-asserts 0.0.1-alpha.26, and nothing links that literal to pubspec.yaml. The next magic_starter bump turns the job red with "expected magic_starter 0.0.1-alpha.26" on a PR whose author has no reason to look in ci.yml. Deriving the expected version from pubspec.yaml's constraint, or asserting "not older than the constraint floor", would keep the same guarantee without the second edit site.

lib/app/commands/_index.g.dart:6AGENTS.md lists this file as generated by commands:refresh and never hand-edited, but the committed content is not dart format clean (a stray trailing blank line after ];), which suggests it was written by hand rather than regenerated. Harmless today (no CI format gate), but the next commands:refresh will produce a diff nobody expects. dart format --output=none --set-exit-if-changed lib/Changed lib/app/commands/_index.g.dart.

Tests

Good coverage for the new surface: 16 cases for app:rename (dry-run inertness by snapshot, idempotency, the pbxproj anchoring, the Kotlin move, five refusal paths) and locale_parity_test.dart for en/tr key parity. The gap is the one in the Major above. DashboardController itself has no unit test; widget_test.dart only proves the app boots.

Checks I ran

  • flutter pub get (no pubspec_overrides.yaml present, so hosted resolution) → Got dependencies!, git status --porcelain pubspec.lock empty, grep -cE 'source: (path|git)' pubspec.lock → 0, magic_starter resolved to "0.0.1-alpha.26". All three published-job assertions pass locally.
  • flutter analyzeNo issues found! (ran in 18.4s).
  • flutter test+26: All tests passed!.
  • bin/design-tokensno raw color literals found outside the allowlist.
  • bin/sync-instructions --checkmirrors are up to date.
  • Verified the PR's own claims: all 20 magic_starter.titles.* keys referenced in magic_starter alpha.26's source are present in assets/lang/en.json (0 missing); the only starter trans() keys still absent are billing ones, and billing is false; the six WCAG ratios in example_status_tokens.dart recompute to exactly the values documented (5.93 / 6.40 / 5.02 / 5.38 / 5.57 / 5.02) against the fills in wind_theme.g.dart; tr.json is genuinely translated (4 of 292 strings identical to en, all placeholders); notifications.push.app_id: '' is safe, notification_service_provider.dart:183 returns early on an empty id.
  • Did not run: the dusk pass (the author documents why), the backend/pint half, and bin/check itself (it requires pubspec_overrides.yaml, absent in this checkout).
  • Not reviewed: README.md, AGENTS.md, docs/verification-loop.md, the .github/ instruction mirrors (verified in sync, not read), assets/lang/tr.json and pubspec.lock (checked programmatically, not line by line).

_buildPlan spread the identity rewrites and the Dart-import scan into one list
without merging by path, and the loop re-reads each path from disk. So a file
claimed by both generators produced two planned writes from the same original
source, and _apply performed them in order: the second discarded the first
while the report counted the file as changed twice, with plausible line counts.

lib/main.dart is the only path both can claim. It carries the MagicApplication
title AND is scanned for a `package:` self-import. This repository never
reaches the collision because its own main.dart imports relatively, which is
exactly why the fix has to be structural rather than left to a fixture that
mirrors this tree: a fork that self-imports loses its display name silently.
The test introduces the self-import to reach the case, and fails on the old
code with "the display-name rewrite was discarded by the import rewrite".

The published job now reads the expected version floor out of pubspec.yaml
instead of carrying a literal. A hard-coded alpha.26 would turn the job red on
the next routine bump, in a file the bumping author has no reason to open, and
name a version nobody typed. The comparison is a floor rather than an equality
so a legitimately newer release still passes, verified against alpha.24
(rejected), alpha.26, alpha.27 and 0.0.2.
@anilcancakir
anilcancakir force-pushed the feature/fork-ready-boilerplate branch from 7bd5af2 to 257db66 Compare September 6, 2026 23:49
@anilcancakir

Copy link
Copy Markdown
Member Author

Thanks, the Major is real and now fixed. Two notes where I landed somewhere different.

Major, _buildPlan duplicate paths. Confirmed and fixed in 257db66. Verified the mechanism
rather than the symptom: the two generators spread into one list with no merge, and the planning
loop calls _read(rewrite.path) fresh each iteration, so the second entry starts from the
original source and _apply writes it last. lib/main.dart is indeed the only path both can
claim.

The plan now merges rules per path before planning. The test introduces the package: self-import
into the fixture's main.dart to reach the case this tree cannot, and asserts both rewrites
survive. Negative-controlled by restoring the old loop, where it fails with exactly the reason it
was written to produce:

Expected: contains 'MagicApplication(title: \'Acme App\''
  Actual: 'import \'package:acme_app/config/app.dart\';\n'
  the display-name rewrite was discarded by the import rewrite

Minor, the hard-coded alpha.26. Fair, and mine. The job now reads the floor out of
pubspec.yaml and compares as a floor rather than an equality, so a legitimately newer release
still passes. Checked against alpha.24 (rejected), alpha.26, alpha.27 and 0.0.2. It also refuses
when either value comes back empty, since the comparison would otherwise pass vacuously.

Minor, _index.g.dart formatting. Half right, and the inference does not hold. The file
genuinely is not dart format clean, but it was not hand-edited: running commands:refresh
reproduces the committed bytes exactly. Three other files fail the same check, including the
sibling generated lib/_previews.g.dart, and none of them is touched by this PR:

Changed lib/_previews.g.dart
Changed lib/app/commands/_index.g.dart
Changed lib/config/view.dart
Changed lib/ui/components/tag/tag.recipe.dart

So this is a property of the artisan generators rather than of this change, and it predates the
PR on three of four counts. Acting on it here would mean hand-editing the one file AGENTS.md
marks as never hand-edited. Left alone deliberately; the generator side belongs in the artisan
repo.

On DashboardController having no unit test: agreed, and worth saying why it is thin. Its
only real logic is resetForSession clearing before refetching, which matters across an identity
change that no unit test here can stage. It is on the list for the dusk pass that is documented
as not run.

@kodizm

kodizm Bot commented Sep 6, 2026

Copy link
Copy Markdown

Note

Kodizm (AI-generated). May contain mistakes; verify before acting.

Incremental re-review of 257db66 only: the duplicate-path Major is genuinely fixed at the structural level and the CI floor is now derived rather than typed; one residual edge in the sort -V comparison.

Since my last review: _buildPlan merges rewrite rules per path before planning, a test introduces the package: self-import the fixture previously could not reach, and the published job reads the floor from pubspec.yaml and compares as a floor. The _index.g.dart formatting note is settled by the reply (generator output, reproduced byte-for-byte, three untouched files share it) and I am not repeating it.

Minor

.github/workflows/ci.yml:174 — correctness in the gate itself. sort -V orders a stable release below its own prereleases, the opposite of semver, so with the floor at ^0.0.1-alpha.26 a resolve onto the stable 0.0.1 reads as a downgrade and the job goes red. That version is inside the constraint (^0.0.1-alpha.26 is >=0.0.1-alpha.26 <0.1.0), so pub can pick it the day magic_starter cuts 0.0.1, and the failure would name a version nobody typed — the same class of surprise this rewrite set out to remove. Every other pair I checked behaves: sort --versionsort (GNU coreutils) 9.4; floor/resolved extraction against the real files both print 0.0.1-alpha.26; printf '0.0.1-alpha.26\n0.0.1-alpha.30\n' | sort -V | head -10.0.1-alpha.26 (pass), …\n0.0.2\n0.0.1-alpha.26 (pass), …\n0.0.1\n0.0.1 (false fail), and the alpha.24 walk-back this job exists for sorts below the floor and fails correctly.

Tests

The new case is the right one: it writes the package:magic_example/ self-import into the fixture's main.dart — the state this tree cannot reach — and asserts both the import and the MagicApplication(title:) rewrite survive, with isNot(contains('magic_example')) closing the gap. 17 cases in that file now.

Checks I ran

  • flutter test test/app/commands/app_rename_command_test.dart+17: All tests passed!
  • flutter test+27: All tests passed! (was 26)
  • flutter analyzeNo issues found! (ran in 17.7s)
  • Read _buildPlan, _identityRewrites, _dartImportRewrites, _apply in full: lib/main.dart is still the only path both generators can claim, merged rules apply in order (identity title, then import) against a single read, and each path now lands in changed/unchanged exactly once.
  • Reproduced the CI shell's floor and resolved extraction against the real pubspec.yaml / pubspec.lock (single magic_starter: line, no pubspec_overrides.yaml in this checkout, so this is the hosted graph) plus the sort -V pairs above. I could not run the full scripted job end to end in a scratch directory; the ordering evidence is direct.
  • Not re-run: the backend/pint half, bin/check (needs pubspec_overrides.yaml), the dusk pass. Nothing outside these three files was re-read.

The nine constraints were written against what was published when this branch
opened, and five releases have gone out since: magic 0.0.11, magic_deeplink
0.1.0, magic_notifications 0.3.1, magic_starter 0.0.27 and fluttersdk_artisan
0.0.15.

Two of them were not lagging but EXCLUDING. A caret raises the minor whenever
the major is zero, so magic_deeplink ^0.0.3 ends at 0.1.0 and
magic_notifications ^0.2.0 ends at 0.3.0: the two releases that carry the
cold-start push fix this app is the demonstration of could not be resolved
here at all. Pub does not report that as a conflict, it walks another package
backwards and exits zero, which is the failure this branch exists to close.

The macOS plugin registrant moves with them: magic 0.0.10 took file_picker to
12, which splits the plugin into federated platform packages, so the generated
import is file_picker_darwin rather than file_picker. It is a tracked
generated file and has to match the graph the lock describes.

Resolved hosted with no overrides, which is what the published CI job asserts.
dart analyze clean, 27 tests green.
@kodizm

kodizm Bot commented Sep 13, 2026

Copy link
Copy Markdown

Note

Kodizm (AI-generated). May contain mistakes; verify before acting.

Incremental re-review of 4c3269c only (the dependency bump): the graph resolves cleanly and the lock is exact, but the magic_notifications 0.2.0 -> 0.3.1 move reintroduces the exact bug class this PR set out to kill - two translation keys the new release requires are not in the catalogue, so the notification preferences screen renders raw key paths.

Since my last review: nine pins moved (magic_starter ^0.0.1-alpha.26 -> ^0.0.27, magic_notifications ^0.2.0 -> ^0.3.1, magic ^0.0.9 -> ^0.0.11, magic_deeplink ^0.0.3 -> ^0.1.0, fluttersdk_artisan ^0.0.15), pubspec.lock regenerated, and macos/Flutter/GeneratedPluginRegistrant.swift follows file_picker 12's split into platform packages. The sort -V prerelease edge from my last review is unchanged in ci.yml but now dormant, since the floor it reads is 0.0.27 rather than a prerelease.

Major

assets/lang/en.json (and assets/lang/tr.json) - correctness. magic_notifications 0.3.0 added a bulk card above the preference matrix that calls trans('notifications.bulk_title') and trans('notifications.bulk_description') (notification_preferences_view.dart:295,299). Neither key exists in either catalogue, and Translator.get answers a miss with the key itself, so /settings/notifications - reachable here, features.notifications is true and magic_starter's notification_routes.dart:41 registers it unconditionally - now shows the literal strings notifications.bulk_title and notifications.bulk_description to every fork. The starter's own 0.0.27 changelog names this as required adopter work: "An app installed before this release has its own copy of the catalogue and has to add all five by hand." Three of those five (delete, delete_failed, channel_sms) are already here; these two are not. Evidence: diffing the 20 trans('notifications.*') keys the package references against the flattened catalogue - used 20, missing 4, with bulk_title/bulk_description among them in both en and tr.

Minor

assets/lang/en.json - the same diff also reports notifications.fetch_error (notification_preferences_controller.dart:122, shown by setError when the preferences fetch fails) and notifications.channel_push_unconfigured (notification_preferences_view.dart:569) as absent. I could not establish whether these predate the bump - only 0.3.1 is in the pub cache here - so they may be out of this diff's scope; the 0.3.0 changelog does say the new bulk row carries the channel_push_unconfigured hint too, so this bump makes it render once more per screen. Worth fixing in the same pass either way.

Tests

locale_parity_test.dart holds en and tr to identical key sets, which is why this slipped: both catalogues are missing the same two keys, so symmetry passes. The gap is that nothing compares the catalogue against the keys the packages actually reference - a test asserting every trans('notifications.*') / trans('magic_starter.*') literal in the resolved dependencies has a home in en.json would have caught this bump and the alpha.24 titles problem alike. No other test covers the new graph beyond the suite still passing.

Checks I ran

  • flutter pub get (no pubspec_overrides.yaml in this checkout, so hosted resolution) -> Got dependencies!, then git status --porcelain empty: the committed pubspec.lock and all four generated plugin registrants match what pub.dev resolves, so the published job's git diff --exit-code -- pubspec.lock step passes.
  • flutter analyze -> No issues found! (ran in 23.5s). flutter test -> +27: All tests passed!.
  • bin/design-tokens -> no raw color literals found outside the allowlist. bin/sync-instructions --check -> mirrors are up to date.
  • Key parity against the resolved packages: all 20 magic_starter.titles.* keys still match exactly 20-to-20 under 0.0.27; of the 98 trans('magic_starter.*') keys, the only 49 missing are all billing.* and features.billing is false; the notifications.* result is the Major above.
  • Config parity: the starter keys this app does not declare (http.timeout_seconds, http.max_retries, notifications.external_id_prefix, routes.billing, billing.web_origin) all read through Config.get with a default in magic_starter_config.dart, so none of them is a live gap.
  • magic_deeplink 0.1.0's breaking DeeplinkHandler.handle(source) signature does not reach this app - nothing under lib/ implements a handler, only lib/config/deeplink.dart supplies config.
  • Did not run: bin/check (needs pubspec_overrides.yaml, absent here), the backend/pint half, the dusk pass. Not read line by line: pubspec.lock (verified programmatically via the resolve above) and assets/lang/tr.json (checked programmatically). Nothing outside the three changed files was re-reviewed.

app:rename refused a target whose Kotlin package would sit INSIDE the current
one only by accident: the guard compared the two paths for equality, and
--org=com.fluttersdk.magic_example --name=app names a real identity whose move
is the package into a child of itself. The apply step creates the destination
first and then lists the source, so the rename landed on the directory it had
just made and threw EINVAL after the identity rewrites were already on disk,
leaving a half-renamed tree with git as the only undo. It is refused at
validation time now, and the message names the two-run route that reaches the
same identity without nesting. Equality is excluded, because a display-only
rename leaves the Kotlin path untouched.

appleId camel-cased only the package, so an org carrying an underscore
(com.acme_inc, the ordinary Java encoding of acme-inc.com) produced
com.acme_inc.acmeApp in both pbxproj files, and CFBundleIdentifier takes
alphanumerics, hyphen and period alone. Every segment is camel-cased now. The
report prints androidId and appleId beside the three inputs, because the two
ids no longer match character for character and a dry run that hid them made
the difference a surprise at code-signing time.

lib/config/notifications.dart declared no service worker keys. The web driver
drops an absent key from the config it hands the SDK, which then registers
OneSignalSDKWorker.js at the root scope that a Flutter web build already owns,
so whichever registration lands second wins and push silently never arrives.
The two literals match what notifications:install writes into web/, and a fork
cannot recover them by re-running that command because it refuses to overwrite
an existing config file.

database.enabled stays and gains a comment rather than being removed: nothing
at runtime gates on it, but notifications:configure prints and rewrites it, and
the package's own install stub declares it.

Also fixes the floor gate in ci.yml, which compared with sort -V. GNU
filevercmp orders an empty suffix below -alpha.1, so a walk-back from 0.0.27 to
0.0.27-alpha.1 sorted the floor first and PASSED the assertion that exists to
catch exactly that, and an alpha floor against a stable resolution failed the
opposite way. The comparison is semver now, verified against both directions.
@kodizm

kodizm Bot commented Sep 13, 2026

Copy link
Copy Markdown

Note

Kodizm (AI-generated). May contain mistakes; verify before acting.

Incremental re-review of 653d31e only: all four fixes land correctly - the semver gate now behaves in both directions, the Kotlin nesting refusal fires before any write, appleId camel-cases every segment, and the two service-worker keys match the package's own install constants; one edge in the refusal's suggested remedy.

Since my last review: ci.yml swapped sort -V for an inline semver comparator (this closes the prerelease edge I raised on 257db66), app:rename gained a pre-write nesting guard and a whole-id camel case, _report prints androidId/appleId, and lib/config/notifications.dart declares service_worker_path/service_worker_scope plus a comment on database.enabled.

Minor

lib/app/commands/app_rename_command.dart:170 - maintainability. The refusal's suggested two-run route does not work when the nesting comes from the org absorbing the unchanged package name. With the tree at org=com.acme, package=app, --org=com.acme.app targets com/acme/app/app, which nests, so the guard fires and prints app:rename --name=app: that name is already current, so run one reports Nothing to do, and run two hits the same refusal. The guard is right to refuse; only the remedy is wrong for this shape. A user does have an escape (rename the package to a temporary name first), it just is not the one the message names.

Tests

Both new cases are the right ones and both fail for the stated reason if the guard is removed: the nesting test pins a full-tree snapshot to prove the refusal precedes any write, and the underscored-org test asserts com.acmeInc.acmeApp in both Apple sites while android/app/build.gradle.kts keeps com.acme_inc.acme_app. The display-only test closes the equality case the guard must not catch. Suite is 30, was 27. Nothing covers the new notifications keys, which is reasonable: they are inert here (app_id is empty, so the provider returns before the web driver reads them).

Still open from my last review

The notifications.bulk_title / notifications.bulk_description Major on 4c3269c is unanswered and unchanged: re-ran the key diff against magic_notifications 0.3.1 and both are still absent from assets/lang/en.json and tr.json, along with fetch_error and channel_push_unconfigured.

Checks I ran

  • flutter test -> exit 0, +30: All tests passed! (was 27). flutter analyze -> No issues found! (ran in 20.7s).
  • Extracted the published floor step verbatim from ci.yml and ran it against the real pubspec.yaml/pubspec.lock -> constraint floor: 0.0.27 | resolved: 0.0.27, exit 0. Ran the embedded comparator directly on both directions of the old bug: floor 0.0.27 vs 0.0.27-alpha.1 -> 1 (now correctly rejected, sort -V passed it), floor 0.0.1-alpha.26 vs stable 0.0.1 -> 0 (was a false fail), plus 0.0.28/0.1.0 pass and 0.0.26 fails. The YAML block scalar strips exactly the 10 spaces the python needs, so it is not an IndentationError on a runner.
  • Read _planKotlinMove and _apply against the guard: the nested direction is the only one that would throw, and the ancestor direction stays safe as claimed (createSync is idempotent, listSync sees real children only, the emptied leaf is pruned). The message's own route (--name=app, then --org=...) does resolve for the fixture's shape - the failing shape is the Minor above.
  • Confirmed _camel cannot throw on a trailing or doubled underscore (.where((p) => p.isNotEmpty)), and that every id-bearing file in the tree (grep -rl 'magicExample\|com\.fluttersdk': both pbxproj files, the xcconfig, Runner.rc, linux/CMakeLists.txt, the gradle and MainActivity.kt) has a matching _FileRewrite entry or is covered by the move.
  • Verified the two notifications literals against the package rather than the comment: install_command.dart:168,171 are 'OneSignalSDKWorker.js' and '/onesignal/', onesignal_web_driver.dart:140-148 reads them as nullable and passes null through when absent, and the keys are declared under notifications.push where notification_service_provider.dart:193-197 reads them.
  • Not run: bin/check (needs pubspec_overrides.yaml, absent in this checkout), the backend/pint half, the dusk pass. Not re-reviewed: anything outside these four files.

@anilcancakir
anilcancakir merged commit 23f42f0 into main Sep 13, 2026
7 checks passed
@anilcancakir
anilcancakir deleted the feature/fork-ready-boilerplate branch September 13, 2026 14:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant